/*
#Copyright (c) 2013, Philippe Bordron <philippe.bordron@gmail.com>
#
# This file is part of SIPPER.
#
# DCC is free software: you can redistribute it and/or modify
# it under the terms of the GNU LESSER GENERAL PUBLIC LICENSE as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# DCC is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU LESSER GENERAL PUBLIC LICENSE for more details.
#
# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE
# along with SIPPER.  If not, see <http://www.gnu.org/licenses/>
*/
package combi.wip.sipper.modeling.metabolicNetworks;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import combi.wip.sipper.modeling.Protein;
import combi.wip.sipper.modeling.reactions.Reaction;
import combi.wip.sipper.modeling.reactions.ReactionUtil;

public  class ImplOfMetabolicNetworkWithEnzymes<C,R extends Reaction<C>, E extends Protein> extends MetabolicNetworkWithEnzymes<C,R,E> {

	protected Map<C, Collection<Reaction<C>>> substratToReactions;
	protected Map<C, Collection<Reaction<C>>> productToReactions;
	protected Map<Protein, Collection<Reaction<C>>> catalyse;
	protected Map<Reaction<C>, Collection<Protein>> catalysedBy;

	public ImplOfMetabolicNetworkWithEnzymes()
	{
		super.init();
		substratToReactions = new HashMap<C, Collection<Reaction<C>>>();
		productToReactions = new HashMap<C, Collection<Reaction<C>>>();
		catalyse = new HashMap<Protein, Collection<Reaction<C>>>();
		catalysedBy = new HashMap<Reaction<C>, Collection<Protein>>();
	}

	public ImplOfMetabolicNetworkWithEnzymes(MetabolicNetworkWithEnzymes<C,Reaction<C>,E> meta)
	{
		substratToReactions = new HashMap<C, Collection<Reaction<C>>>();
		productToReactions = new HashMap<C, Collection<Reaction<C>>>();
		catalyse = new HashMap<Protein, Collection<Reaction<C>>>();
		catalysedBy = new HashMap<Reaction<C>, Collection<Protein>>();
		super.deepCopy(meta);
	}


	@Override
	public void addCompound(C cpd) {
		this.compoundSet.put(cpd.toString(), cpd);
	}

	@Override
	public void removeCompound(C cpd) {
		List<Reaction<C>> toRemove = new ArrayList<Reaction<C>>(this.getReactions(cpd));
		for(Reaction<C> r : toRemove)
		{
			this.removeReaction(r);
		}
		this.compoundSet.remove(cpd);
	}

	private void addToMapCToSetR(Map<C, Collection<Reaction<C>>> map, C c, Reaction<C> r)
	{
		Collection<Reaction<C>> set = map.get(c);
		if (set == null)
		{
			set = new HashSet<Reaction<C>>();
			map.put(c, set);
		}
		set.add(r);
	}

	private void removeFromMapCToSetR(Map<C, Collection<Reaction<C>>> map, C c, Reaction<C> r)
	{
		Collection<Reaction<C>> set = map.get(c);
		if (set != null)
		{
			set.remove(r);
			if (set.isEmpty())
			{
				map.remove(c);
			}
		}
	}

	@Override
	public void addReaction(Reaction<C> reaction) {
		for (C c : reaction.getSubstrats())
		{
			this.addCompound(c);
			this.addToMapCToSetR(this.substratToReactions, c, reaction);
		}
		for (C c : reaction.getProducts())
		{
			this.addCompound(c);
			this.addToMapCToSetR(this.productToReactions, c, reaction);
		}
		this.reactionSet.put(reaction.getId(),reaction);
	}

	@Override
	public void updateReaction(Reaction<C> reaction) {
		if (reactionSet.containsKey(reaction))
		{
			this.removeReaction(reaction);
		}
		addReaction(reaction);
	}


	@Override
	public void removeReaction(Reaction<C> reaction) {
		Collection<C> toRemove = this.getSubstrats(reaction);
		for(C c : toRemove)
		{
			removeFromMapCToSetR(this.substratToReactions, c, reaction);
		}
		toRemove = this.getProducts(reaction);
		for(C c : toRemove)
		{
			removeFromMapCToSetR(this.productToReactions, c, reaction);
		}

		Collection<Protein> toRemoveEnz = new HashSet<Protein>();
		for(Protein enz : this.getEnzymes(reaction))
		{
			toRemoveEnz.add(enz);
		}
		for (Protein enz : toRemoveEnz)
		{
			this.removeCatalyze(enz,reaction);
		}
		this.reactionSet.remove(reaction);
	}

	@Override
	public void addEnzyme(Protein enz) {
		this.enzymeSet.put(enz.getId(), enz);
	}

	@Override
	public void removeEnzyme(Protein enz) {
		List<Reaction<C>> toRemove = new ArrayList<Reaction<C>>(this.getCatalysedReactions(enz));
		for(Reaction<C> r : toRemove)
		{
			this.removeCatalyze(enz,r);
		}
		this.enzymeSet.remove(enz);
	}

	@Override
	public void addCatalyze(Protein enz, Reaction<C> reaction) {
		if (this.getReactionSet().contains(reaction))
		{
			this.addEnzyme(enz);
			Collection<Reaction<C>> reacts = this.catalyse.get(enz);
			if (reacts == null)
			{
				reacts = new HashSet<Reaction<C>>();
				this.catalyse.put(enz,reacts);
			}
			reacts.add(reaction);
			Collection<Protein> enzymes = this.catalysedBy.get(reaction);
			if (enzymes == null)
			{
				enzymes = new HashSet<Protein>();
				this.catalysedBy.put(reaction,enzymes);
			}
			enzymes.add(enz);
		}
	}

	@Override
	public void removeCatalyze(Protein enz, Reaction<C> reaction) {
		Collection<Reaction<C>> reacts = this.catalyse.get(enz);
		if (reacts != null)
		{
			reacts.remove(reaction);
			if(reacts.isEmpty())
			{
				this.catalyse.remove(enz);
			}
		}
		Collection<Protein> enzymes = this.catalysedBy.get(reaction);
		if (enzymes != null)
		{
			enzymes.remove(enz);
			if(enzymes.isEmpty())
			{
				this.catalysedBy.remove(enz);
			}
		}
	}

	@Override
	public Collection<Reaction<C>> getCatalysedReactions(Protein enz) {
		Collection<Reaction<C>> result = catalyse.get(enz);
		if (result == null)
		{
			result = new HashSet<Reaction<C>>();
		}
		return result;
	}

	@Override
	public Collection<Protein> getEnzymes(Reaction<?> reaction) {
		Collection<Protein> result = catalysedBy.get(reaction);
		if (result == null)
		{
			result = new HashSet<Protein>();
		}
		return result;
	}

	@Override
	public Collection<C> getCompounds(Reaction<C> reaction) {
		return reaction.getCompounds();
	}

	@Override
	public Collection<C> getSubstrats(Reaction<C> reaction) {
		return reaction.getSubstrats();
	}

	@Override
	public Collection<C> getProducts(Reaction<C> reaction) {
		return reaction.getProducts();
	}

	@Override
	public Collection<Reaction<C>> getReactions(C cpd) {
		Collection<Reaction<C>> result = new HashSet<Reaction<C>>();
		result.addAll(this.whichProduce(cpd));
		result.addAll(this.whichConsume(cpd));
		return result;
	}

	@Override
	public Collection<Reaction<C>> whichProduce(C cpd) {
		Collection<Reaction<C>> result = new HashSet<Reaction<C>>();
		Collection<Reaction<C>> tmp =	this.productToReactions.get(cpd);
		if (tmp != null)
		{
			result.addAll(tmp);
		}
		return result;
	}

	@Override
	public Collection<Reaction<C>> whichConsume(C cpd) {
		Collection<Reaction<C>> result = new HashSet<Reaction<C>>();
		Collection<Reaction<C>> tmp =	this.substratToReactions.get(cpd);
		if (tmp != null)
		{
			result.addAll(tmp);
		}
		return result;
	}

	@Override
	public void clear()
	{
		super.clear();
		enzymeSet.clear();
		substratToReactions.clear();
		productToReactions.clear();
		catalyse.clear();
		catalysedBy.clear();
	}

	public static void main(String[] args)
	{
		MetabolicNetworkWithEnzymes<String, Reaction<String>, Protein> meta= new ImplOfMetabolicNetworkWithEnzymes<String, Reaction<String>, Protein>();
		String c1 = "c1";
		meta.addCompound(c1);
		System.out.println("ajout dans meta : "+c1);
		System.out.println("meta : "+meta.toString());
		System.out.println(meta.toString());

		String c2 = "c2", c3 = "c3";
		Collection<String> pg = new HashSet<String>();
		pg.add(c1);
		Collection<String> pd = new HashSet<String>();
		pd.add(c2);pd.add(c3);
		Reaction<String> r1 = new Reaction<String>("r1", pg, pd, ReactionUtil.Orientation.LR);
		System.out.println("ajout dans meta : "+r1);
		meta.addReaction(r1);
		System.out.println("meta : "+meta.toString());
		//		System.out.println("meta.getCompounds(r1) : "+meta.getCompounds(r1).toString());
		//		System.out.println("meta.getReactions(c1) : "+meta.getReactions(c1).toString());
		//		System.out.println("meta.whichConsume(c1) : "+meta.whichConsume(c1).toString());
		//		System.out.println("meta.whichProduce(c1) : "+meta.whichProduce(c1).toString());


		String e1 = "e1";
		System.out.println("ajout catalyse : "+e1 +" -- "+ r1);
		Protein p1 = new Protein(e1);
		meta.addCatalyze(p1,r1);
		System.out.println("meta : "+meta.toString());



		System.out.println("copie de meta dans meta2 et meta3");
		MetabolicNetworkWithEnzymes<String, Reaction<String>, Protein> meta2 = new ImplOfMetabolicNetworkWithEnzymes<String, Reaction<String>, Protein>(meta);
		MetabolicNetwork<String, Reaction<String>> meta3 = new ImplOfMetabolicNetwork<String, Reaction<String>>(meta);
		System.out.println("meta : "+meta.toString());
		System.out.println("meta2 : "+meta2.toString());
		System.out.println("meta3 : "+meta3.toString());
		//		System.out.println("meta3.getCompounds(r1) : "+meta3.getCompounds(r1).toString());
		//		System.out.println("meta3.getReactions(c1) : "+meta3.getReactions(c1).toString());
		//		System.out.println("meta3.whichConsume(c1) : "+meta3.whichConsume(c1).toString());
		//		System.out.println("meta3.whichProduce(c1) : "+meta3.whichProduce(c1).toString());

		System.out.println("retrait de meta3 : "+c1);
		meta3.removeCompound(c1);
		System.out.println("meta : "+meta.toString());
		System.out.println("meta2 : "+meta2.toString());
		System.out.println("meta3 : "+meta3.toString());

		System.out.println("retrait de meta : "+c1);
		meta.removeCompound(c1);
		System.out.println("meta : "+meta.toString());
		System.out.println("meta2 : "+meta2.toString());

		System.out.println("test si e1 ne catalyse plus r1 dans meta");
		System.out.println("meta : "+meta.getCatalysedReactions(p1).toString());


		System.out.println("retrait de meta 2 : "+r1);
		meta2.removeReaction(r1);
		System.out.println("meta : "+meta.toString());
		System.out.println("meta2 : "+meta2.toString());

		System.out.println("clear de meta 2 : "+r1);
		meta2.clear();
		System.out.println("meta : "+meta.toString());
		System.out.println("meta2 : "+meta2.toString());
	}
}
